<?php
/**
 * 数据库操作 (PDO)
 * 
 * @copyright 139.com Inc
 */

class ApiDB {

	static private $mConnection = array();
	static private $mInstance	= array();

	private $_lastConnect= null;
	private $_connection = array();
	private $_config 	 = array();

	/**
	 * see @BuildCondition 
	 */
	static private $_params 	= array();

	/**
	 * 获取唯一实例
	 *
	 * @param array config 	db config param
	 * @return instance of object
	 */
	static function Instance($config = array()) {
		$key 	= serialize($config);

		if(!isset(self::$mInstance[$key]) or empty(self::$mInstance[$key]) or empty(self::$mConnection))  
			self::$mInstance[$key] = new ApiDB($config);

		return self::$mInstance[$key];
	}
	
	/**
	 * 构造方法
	 */
	private function __construct($config = array()) {
		$this->_config 	= $config;
		$this->Connect();
	}

	function Connect() {
		if(!empty($this->_connection))
			return;

		$option = array(
			PDO::ATTR_EMULATE_PREPARES   => true,
			PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",
		);

		$config = $this->_config;
		foreach($config as $i => $database) {
			$dsn = isset($database[4]) ?
                "mysql:dbname={$database[3]};host={$database[0]};port={$database[4]}" :
                "mysql:dbname={$database[3]};host={$database[0]}";
			try {
				$this->_connection[$i] = new PDO($dsn, $database[1], $database[2], $option);
			} catch(Exception $e ) {
				throw new Exception('Connect ' .$dsn. ' failed: ' . $e->getMessage());
			}
		}
	}

	/**
	 * 析构方法
	 */
	function __destruct() {
		$this->Close();
	}

	/**
	 * 关闭
	 */
	function Close() {
		$this->_connection 	= array();
	}

	/**
	 * 获取最后插入ID
	 */
	function GetInsertId() {
		$i = isset($this->_lastConnect) ? $this->_lastConnect : 0; 
		return is_object($this->_connection[$i]) ? 
			(int)$this->_connection[$i]->lastInsertId() : false; 
	}

	/**
	 * get db connection
	 *
	 * @param bool $isSelect 指定db
	 * @return object
	 */
	private function _getConn($isSelect = false) {
		$i 						= rand(0, count($this->_config) - 1);
		$this->_lastConnect 	= ($isSelect && $i) ? $i : 0;
		if(!is_object($this->_connection[$this->_lastConnect])) 
			$this->Connect();
		$conn 					= $this->_connection[$this->_lastConnect]; 
		return $conn;
	}

	/**
	 * 组建QueryCondition
	 *
	 * @param mix $condition;
	 * @param string $logic, optional
	 * @return string $condition
	 */
	static function BuildCondition($condition=array(), $logic='AND') {
		if ( is_string( $condition ) || is_null($condition) )
			return $condition;

		$logic = strtoupper( $logic );
		$content = null;
		foreach ( $condition as $k => $v ) {
			$v_str = ' ? ';
			$v_connect = '=';

			if ( is_numeric($k) ) {
				$content .= ' '. $logic . ' (' . self::BuildCondition( $v ) . ')';
				continue;
			}

			$maybe_logic = strtoupper($k);
			if ( in_array($maybe_logic, array('AND','OR'))) {
				$content .= $logic . ' (' . self::BuildCondition( $v, $maybe_logic ) . ')';
				continue;
			}

			if ( is_numeric($v) ) {
				self::$_params[] = $v;	
			} else if ( is_null($v) ) {
				$v_connect = ' IS ';
				$v_str = 'NULL';
			} else if ( is_array($v) && ($c = count($v))) {
				if (1<$c) {
					self::$_params = array_merge(self::$_params, $v);
					$v_connect 	= 'IN(' . join(',', array_fill(0, $c, '?')) . ')';
					$v_str		= '';
				} else if ( empty($v) ) {
					$v_str = $k;
					$v_connect = '<>';
				} else {
					$tmp_keys = array_keys($v);
					$v_connect = array_shift($tmp_keys);
					if( is_numeric($v_connect) )
						$v_connect = '=';
					$tmp_values = array_values($v);	
					$v_s = array_shift($tmp_values);

					if(is_array($v_s)) {
						$v_str = 'IN (' . join(',', array_fill(0, count($v_s), '?')) . ')';
						self::$_params = array_merge(self::$_params, $v_s);
					} else {
						self::$_params[] = $v_s;	
					}

				}
			} else {
				self::$_params[] = $v;
			}

			$content .= " $logic `$k` $v_connect $v_str ";

		}

		$content = preg_replace( '/^\s*'.$logic.'\s*/', '', $content );
		$content = preg_replace( '/\s*'.$logic.'\s*$/', '', $content );
		$content = trim($content);

		return $content;
	}


	/**
	 * 根据条件获取一条记录
	 * @param string $table 表名
	 * @param mix $condition 条件
	 * @param array $option 查询选项
	 * @return record
	 */
	public function GetTableRow($table, $condition, $options=array()) {
		return $this->LimitQuery($table, array(
			'condition' => $condition,
			'one' 		=> isset($options['one']) ? $options['one'] : true,
			'select' 	=> isset($options['select']) ? $options['select'] : '*',
		));
	}

	/**
	 * 根据条件获取有限条数记录
	 * @param string $table 表名
	 * @param array $options 查询选项
	 $options 可以包含 cache 选单，表示记录cache时间
	 * @return array of record
	 */
	function LimitQuery($table, $options=array()) {
		return $this->DBLimitQuery($table, $options);
	}
	
	/**
	 * 根据条件获取有限条数记录，从库中查询，并进行缓存 
	 *
	 * @param string $table 表名
	 * @param array $option 查询选项
	 * @return array of record
	 */
	function DBLimitQuery($table, $options=array()) {
		$condition 	= isset($options['condition']) ? $options['condition'] : null;
		$one 		= isset($options['one']) ? $options['one'] : false;
		$offset 	= isset($options['offset']) ? abs(intval($options['offset'])) : 0;

		if($one) 
			$size = 1;
		else 
			$size = isset($options['size']) ? abs(intval($options['size'])) : null;

		$order 		= isset($options['order']) ? $options['order'] : null;
		$select 	= isset($options['select']) ? $options['select'] : '*';

		$condition 	= self::BuildCondition( $condition );
		$condition 	= (null==$condition) ? null : "WHERE $condition";

		if($one)
			$limitation = " LIMIT 1 ";
		else
			$limitation = $size ? "LIMIT $offset,$size" : null;

		$sql = "SELECT $select FROM `$table` $condition $order $limitation";
		return $this->GetQueryResult($sql, $one, self::$_params);
	}

	/**
	 * 执行真正的数据库查询
	 * @param string $sql
	 * @param bool $one 是否单条记录
	 * @return array of $record
	 */
	function GetQueryResult($sql, $one=true, array $params = array()) {
		$ret 	= array();
		$stmt 	= $this->Execute($sql, $params);
		if(!is_object($stmt)) {
			error_log('Error: bad sql - ' . $sql);
			error_log('Error: bad sql - ' . var_export($params, true));
			return array();
		} else {
			return $one ? $stmt->fetch() : $stmt->fetchAll();
		}
	}

	/**
	 * 插入一条记录 Alias of method: Insert
	 * @param string $table 表名
	 * @param array $condition 记录
	 * @return int $id
	 */
	function SaveTableRow($table, $condition) {
		return $this->Insert($table, $condition);
	}

	/**
	 * 插入一条记录
	 * @param string $table 表名
	 * @param array $condition 记录
	 * @return int $id
	 */
	function Insert($table, $condition, $type=0) {
		$content 	= null;
		$sql = "INSERT INTO `$table` 
				(`" . join('`,`', array_keys($condition)) . '`) 
				values (' . join(',', array_fill(0, count($condition), '?')) . ')';

		$stmt = $this->Execute($sql, array_values($condition));
		if(1==$type) //用于不是自增id时的判断
			return is_object($stmt);
		$insertId = $this->GetInsertId();

		return $insertId;
	}

	/**
	 * 删除一条记录 Alias of method: Delete
	 * @param string $table 表名
	 * @param array $condition 条件 
	 * @return int $id
	 */
	function DelTableRow($table=null, $condition=array()) {
		return $this->Delete($table, $condition);
	}

	/**
	 * 删除一条记录
	 * @param string $table 表名
	 * @param array $condition 条件 
	 * @return int $id
	 */
	function Delete($table=null, $condition = array()) {
		if ( null==$table || empty($condition) )
			return false;

		$condition = self::BuildCondition($condition);
		$condition = (null==$condition) ? null : "WHERE $condition";
		$sql = "DELETE FROM `$table` $condition";

		$flag = $this->Execute($sql, self::$_params);
		return $flag;
	}

	function Execute($sql, array $params = array(), $retry = 0) {
		try {
			$conn	= $this->_getConn();
			$sth 	= $conn->prepare($sql);
			if(!is_object($sth)) 
				throw new Exception('Error: bad sql');
			$sth->setFetchMode(PDO::FETCH_ASSOC);
			$result = empty($params) ? $sth->execute() : $sth->execute(array_values($params));
			if(($sth->errorCode() == 2006) and !$retry) {
				$this->Close();
				$this->Execute($sql, $params, $retry = 1);
			}
		} catch(Exception $e) {
			$result = false;
		}

		self::$_params = array();
		if ( false == $result ) {
			$this->Close();
			return false;
		}

		return $sth;
	}

	/**
	 * 更新一条记录
	 * @param string $table 表名
	 * @param mix $id 更新条件 
	 * @param mix $updaterow 修改内容
	 * @param string $pkname 主键 
	 * @return boolean
	 */
	function Update( $table=null, $id=1, $updaterow=array(), $pkname='id' ) {
		if ( null==$table || empty($updaterow) || null==$id)
			return false;

		if ( is_array($id) ) $condition = self::BuildCondition($id);
		else $condition = "`$pkname`='$id'";

		$sql 		= "UPDATE `$table` SET ";
		$content 	= null;
		$updates 	= array();
		$v_str		= '?';

		foreach ( $updaterow as $k => $v ) {
			if(is_array($v)) {
				$str = $v[0]; //for 'count'=>array('count+1');
				$content .= "`$k`=$str,";
			} else {
				$updates[] 	= $v;
				$content .= "`$k`=$v_str,";
			}
		}

		$content 	= trim($content, ',');
		$sql 		.= $content;
		$sql 		.= " WHERE $condition";
		$result = $this->Execute($sql, array_merge($updates, self::$_params));

		return is_object($result) ? $result->rowCount() : false;
	}

	/**
	 * 是否存在符合条件的记录
	 * @param string $table 表名
	 * @param array $condition
	 * @param boolean $returnid 是否返回记录id
	 * @return mixed (int)id /(array)record
	 */
	function Exist($table, $condition=array(), $returnid = true, $order='') {
		$row = $this->LimitQuery($table, array(
			'condition' => $condition,
			'one' => true,
			'order' => $order
		));

		if($returnid)
			return empty($row) ? false : (isset($row['id']) ? $row['id'] : true);
		else
			return empty($row) ? array() : $row;
	}

	static function CheckInt(&$id, $is_abs=false) {
		if ( is_array($id) ) {
			foreach( $id AS $k => $o ) $id[$k] = self::CheckInt($o);
			return $id;
		}

		if(!is_int($id))
			$id = intval($id);

		if(0>$id && $is_abs)
			return abs($id);
		else
			return $id;
	}

    /**
     * 检查是否DB用于的Array
     * @param mix $arr
     * @return int $arr
     */
	static function CheckArray(&$arr) {
        if ( !is_array($arr) ) {
			if(false===$arr)
				$arr = array();
			else
				settype($arr, 'array');
		}
		return $arr;
	}

	public function Query($sql ,$isSelect=false) {
		$result = $this->_getConn($isSelect)->query($sql);
		if($result) 
			return $result;

		self::Close();
		return false;
	}
}
